#if defined(_MSC_VER)
#pragma once
#endif

#ifndef LM_CORE_RAY_H
#define LM_CORE_RAY_H

#include "vector3.h"
#include "point.h"

class Ray
{
public:
	Ray() : time(0), mint(EPSILON(6)), maxt(INFINITY), depth(0) { }
	Ray(const Ray &ray) : o(ray.o), d(ray.d), dRcp(ray.dRcp), time(ray.time), mint(ray.mint), maxt(ray.maxt), depth(ray.depth) { }
	Ray(const Point3 &origin, const Vector3 &dir, float t = 0, float t0 = EPSILON(6), float t1 = INFINITY, int dpth = 0) :
	o(origin), d(dir), time(t), mint(t0), maxt(t1), depth(dpth) {
		dRcp.x = 1.f / d.x; dRcp.y = 1.f / d.y; dRcp.z = 1.f / d.z;
	}
	Ray(const Point3 &origin, const Vector3 &dir, const Ray &ray, float t0 = EPSILON(6), float t1 = INFINITY, int dpth = 0) :
	o(origin), d(dir), time(ray.time), mint(t0), maxt(t1), depth(ray.depth+1) { 
		dRcp.x = 1.f / d.x; dRcp.y = 1.f / d.y; dRcp.z = 1.f / d.z;
	}
	Point3 operator()(float t) const {
		return o + d * t;
	}
	std::string ToString() const {
		std::ostringstream oss;
		oss << "Ray from " << o.ToString() << " to "<< d.ToString() << "\nwith time = "<<time<<" mint = "<<mint<<" maxt = "<<maxt<< " depth = " << depth;
		oss<< "\nRay dRcp " << dRcp.ToString();
		return oss.str();
	}
	// Public data of ray
	Point3 o;			// origin of the ray
	Vector3 d;			// direction of the ray
	Vector3 dRcp;		// reciprocals of the ray direction
	float time;			// time of the ray, for motion blur
	mutable float mint;	// mint - eps
	mutable float maxt;	// maxt - infinity
	int depth;			// depth - 0 
};
class RayDifferential : public Ray
{
public:
	RayDifferential () : hasDifferentials(false) { }
	RayDifferential (const Point3 &origin, const Vector3 &dir, float t = 0, float t0 = EPSILON(6), float t1 = INFINITY, int dpth = 0) :
	Ray(origin,dir,t,t0,t1,dpth), hasDifferentials(false) { }
	explicit RayDifferential(const Ray &ray) : Ray(ray), hasDifferentials(false) { }
	RayDifferential(const Point3 &origin, const Vector3 &dir, const Ray &ray, float t0 = EPSILON(6), float t1 = INFINITY, int dpth = 0):
	Ray(origin,dir,ray,t0,t1,dpth), hasDifferentials(false) { }
	RayDifferential(const RayDifferential &ray) : Ray(ray), hasDifferentials(ray.hasDifferentials), 
		rxOrigin(ray.rxOrigin), ryOrigin(ray.ryOrigin), rxDirection(ray.rxDirection), ryDirection(ray.ryDirection) { }
	RayDifferential &operator=(const RayDifferential &ray) {
		o = ray.o; d = ray.d; dRcp = ray.dRcp;
		time = ray.time; mint = ray.mint; maxt = ray.maxt;
		hasDifferentials = ray.hasDifferentials;
		rxOrigin = ray.rxOrigin; ryOrigin = ray.ryOrigin;
		rxDirection = ray.ryDirection; ryDirection = ray.ryDirection;
		return *this;
	}
	RayDifferential &operator=(const Ray &ray) {
		o = ray.o; d = ray.d; dRcp = ray.dRcp;
		time = ray.time; mint = ray.mint; maxt = ray.maxt;
		hasDifferentials = false;
		return *this;
	}
	void ScaleDifferential(float amount) {
		rxOrigin = (o + (rxOrigin - o) * amount);
		ryOrigin = (o + (ryOrigin - o) * amount);
		rxDirection = (d + (rxDirection - d) * amount);
		ryDirection = (d + (ryDirection - d) * amount);
	}
	std::string ToString() const {
		std::ostringstream oss;
		oss << "RayDifferential\n";
		oss << "Ray from " << o.ToString() << " to "<< d.ToString() << "\nwith time = "<<time<<" mint = "<<mint<<" maxt = "<<maxt<< " depth = " << depth;
		oss<< "\nRay dRcp " << dRcp.ToString()<<"\n";
		if(hasDifferentials)
		{
			oss << "Rx from " << rxOrigin.ToString() << " with direction " << rxDirection.ToString() << "\n";
			oss << "Ry from " << ryOrigin.ToString() << " with direction " << ryDirection.ToString();
		} else
			oss << "No Differentials";
		return oss.str();
	}
	bool hasDifferentials;
	Point3 rxOrigin, ryOrigin;
	Vector3 rxDirection, ryDirection;
	// Ray rx,ry;	// we don't need all information in Ray
};

#endif	// LM_CORE_RAY_H